package com.zx.crowdfunding.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;

import com.zx.crowdfunding.global.CrowdFundingGlobal;
import com.zx.crowdfunding.service.api.AuthService;

/**
 * 自定义的SpringSecurity资源授权的数据源
 * 模仿DefaultFilterInvocationSecurityMetadataSource类
 * 为了实现动态权限控制，不用在Controller的方法上加注解，解决了硬编码的问题
 * @author 郑雪
 * @date 2021-12-14
 */
@Component
public class CrowdFundingFilterInvocationSecurityMetadataSource implements
		FilterInvocationSecurityMetadataSource {
	
	/**
	 * 日志
	 */
	protected final Log logger = LogFactory.getLog(getClass());
	
	/**
	 * 权限Service接口
	 */
	@Autowired
	private AuthService authService;

	/**
	 * 资源权限集合
	 */
	private Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
	
	/**
	 * 构造器
	 * @param requestMap
	 */
	public CrowdFundingFilterInvocationSecurityMetadataSource(
			LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
		
		this.requestMap = requestMap;
	}
	
	/**
	 * 查询数据库，加载资源权限集合
	 */
	public void loadRequestMap(){
		
		// 如果需要更新资源权限集合
		if(CrowdFundingGlobal.UPDATE_RESOURCE_AUTH){
			
			// 创建一个Map对象，用来存放资源权限集合
			requestMap = new HashMap<RequestMatcher, Collection<ConfigAttribute>>();
			
			// 获取全局静态变量中的资源权限集合
			List<Map<String, Object>> resourceAuthList = CrowdFundingGlobal.resourceAuthList;
			
			// 从数据中查询出来的资源权限集合，resource：资源路径（支持Ant风格）；authorize：权限的SPEL表达式
			resourceAuthList = authService.getAllResourceAuth();
			
			// 遍历
			for (Map<String, Object> map : resourceAuthList) {
				
				// 请求资源的路径
				String resource = (String) map.get("resource");
				resource = (resource.startsWith("/")?"":"/") + resource;
				
				// 请求资源的权限SPEL表达式字符串
				String authorize = (String) map.get("authorize");
				
				// 请求匹配器
				RequestMatcher antRequestMatcher = new AntPathRequestMatcher(resource);
				
				// 权限集合
				ArrayList<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(1);
				
				// 将表达式放入权限集合
				attributes.add(new CrowdFundingConfigAttribute(null, null, authorize));
				
				requestMap.put(antRequestMatcher, attributes);
			}
			
			CrowdFundingGlobal.UPDATE_RESOURCE_AUTH = false;
			logger.info("加载资源权限 ：" + requestMap);
			
		}
		
	}

	/**
	 * 获取某个资源的权限集合
	 */
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object)
			throws IllegalArgumentException {
		
		// 获取请求
		final HttpServletRequest request = ((FilterInvocation) object).getRequest();
		
		// 加载资源权限集合，需要更新时才会查询数据库
		loadRequestMap();
		
		// 遍历requestMap
		for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
				.entrySet()) {
			
			// 判断是否与当前请求的路径匹配
			if (entry.getKey().matches(request)) {
				
				// 返回当前请求的权限集合
				return entry.getValue();
			}
		}
		
		// 如果没找到，则返回null
		return null;
	}

	/**
	 * 获取所有的权限集合
	 */
	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		
//		// 所有权限集合
//		Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
//
//		// 加载资源权限集合，需要更新时才会查询数据库
//		loadRequestMap();
//			
//		// 遍历requestMap
//		for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
//				.entrySet()) {
//			allAttributes.addAll(entry.getValue());
//		}
//
//		// 返回所有权限集合
//		return allAttributes;
		
		return null;
	}

	@Override
	public boolean supports(Class<?> clazz) {
		return FilterInvocation.class.isAssignableFrom(clazz);
	}

}
